#include <sys/types.h>	/* key_t */
#include <sys/ipc.h>	/* ftok */
#include <sys/msg.h>	/* msgget msgctl msgsnd msgrcv */
#include <stdio.h>		/* perror */
#include <string.h>		/* memcpy */
#include "comm.h"

#define MSG_QUEUE_PATH "/tmp"
#define MSG_QUEUE_PROJECT_ID 1

/* 必须定义类似此结构的消息结构体，其中mtype有特定的操作含义；
   每条消息允许的最大长度还由操作系统限定，可在系统中用命令查看最大的允许值；
   用cat /proc/sys/kernel/msgmax命令查看单个消息队列的最大长度，
   我的电脑上是8k，用cat /proc/sys/kernel/msgmnb查看单个队列的最大总长度，
   我的电脑上是16k，这些最大值可以自行配置修改 */
typedef struct msgbuf {
    long mtype;
    char mtext[MSG_QUEUE_MAXSIZE];
} msgbuf_t;

static int set_msg_queue(int flags)
{
	/* 在指定目录下获取虚拟内存文件的id值，文件的权限为可读写；
	   也可以不用ftok函数获取key，可以直接给key赋值一个随机值就能运行 */
	key_t key = ftok(MSG_QUEUE_PATH, MSG_QUEUE_PROJECT_ID);
	if (key < 0) {
		perror("ftok");
		return -1;
	}

	//!!! 如果server msgget()报错文件已存在，可以每次手动给key赋新的不同的随机值再尝试
	//key_t key = 0x1111;

	/* 创建或者获取消息队列，给出创建参数 */
	int msg_id = msgget(key, flags);
	if (msg_id < 0) {
		perror("msgget");
		printf("please call destroy_msg_queue(), or change the key value and try again\n");
		return -1;
	}

	printf("msg_id is %d\n", msg_id);

	return msg_id;
}

int create_msg_queue(void)
{
	/* 创建消息队列，如果不存在则创建，如果存在则报错；
	   消息队列文件的权限为可读写 */
	return set_msg_queue(IPC_CREAT | IPC_EXCL | 0664);
}

int get_msg_queue(void)
{
	/* 获取消息队列，如果存在则返回，如果不存在则创建 */
	return set_msg_queue(IPC_CREAT);
}

/* server中必须要每次都正确执行到此函数，否则下次运行会报文件已存在错误 */
int destroy_msg_queue(int msg_id)
{
	/* 销毁消息队列 */
	int ret = msgctl(msg_id, IPC_RMID, NULL);
	if (ret < 0) {
		perror("msgctl");
		return -1;
	}
	
	return 0;
}

int send_msg_queue(int msg_id, int channel_id, char *msg, int len)
{
	struct msgbuf mbuf;
	
	if (!msg || len > sizeof(mbuf.mtext)) {
		printf("param %p %d error\n", msg, len);
		return -1;
	}

	mbuf.mtype = channel_id;
	memcpy(mbuf.mtext, msg, len);	/* 真正在项目中使用时可以直接将msg参数替换成mbuf参数，这样可以省一次拷贝的时间 */

	/* 发送消息 */
	int ret = msgsnd(msg_id, (void *)&mbuf, len, 0);
	if (ret < 0) {
		perror("msgsnd");
		return -1;
	}

	return 0;
}

int recv_msg_queue(int msg_id, int channel_id, char *recv_buf)
{
	struct msgbuf mbuf;
	
	if (!recv_buf) {
		printf("param %p error\n", recv_buf);
		return -1;
	}

	ssize_t len = msgrcv(msg_id, (void *)&mbuf, sizeof(mbuf.mtext), channel_id, 0);
	if (len < 0) {
		perror("msgrcv");
		return -1;
	}

	memcpy(recv_buf, mbuf.mtext, len);	/* 真正在项目中使用时可以直接将msg参数替换成mbuf参数，这样可以省一次拷贝的时间 */

	return len;	/* 返回真正读到mtext中的长度 */
}
